package mw.live.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import mw.client.dialogs.ErrorView;
import mw.client.server.MWServerStarter;

/**
 * Magic Wars Live Server
 * Based on IRC server
 */
public class LiveServer extends Thread {

	private static List<PrintWriter> manager = Collections.synchronizedList(new ArrayList<PrintWriter>());
	
	private ArrayList<ClientStatus> clientList = new ArrayList<ClientStatus>(0);
	public boolean shutdown = false;
	public ArrayList<ChatThread> clients = new ArrayList<ChatThread>(0);
	public static ServerSocket server;
	private boolean http = false;
	
	private static String liveHost;
	private static int livePort;
	
	private Set<Integer> ports = new HashSet<Integer>();
	private HashMap<String, GameInfo> games = new HashMap<String, GameInfo>();
	private String topic = "MWLive: MagicWars game server. mailto: magicwars2k@gmail.com";

	public LiveServer(int port) {
		loadSettings("mwlive.properties");
		String dburl = "" + getClass().getResource("auth.db");
		if (dburl.length() >= 5 && dburl.substring(0, 5).equals("file:")) {
			dburl = dburl.substring(5);
		}
		livePort = port;
		livePort = port;
		
		try {
			Class.forName("org.codehaus.groovy.control.CompilerConfiguration");
		} catch (ClassNotFoundException ce) {
			ErrorView.showError(ce, ce.getMessage());
		}
	}

	public void startServer() {
		try {
			server = new ServerSocket(livePort);
			//InetAddress addr = InetAddress.getLocalHost();
			//System.out.println("MW Live Server started @ " + addr.getHostName() + "/" + addr.getHostAddress() + ":" + port);
			System.out.println("MW Live Server started @ " + liveHost + ":" + livePort);

			while (!this.shutdown) {
				Socket client = server.accept();
				if (!isBanned(client.getInetAddress().getHostAddress())) {
					ChatThread cl = new ChatThread(client);
					this.clients.add(cl);
					cl.start();
				}
			}

		} catch (IOException e) {
			System.err.println(e);
		}
	}

	public void stopServer() {
		this.shutdown = true;
		System.out.println("Shutting down...");
		for (int i = 0; i < this.clients.size(); i++) {
			((ChatThread) this.clients.get(i)).out.println("none~.~server~.~none~.~!connectionClosed");
		}
		closeServer();
	}

	public void closeServer() {
		try {
			server.close();
		} catch (IOException e) {
		} finally {
			System.exit(0);
		}
	}
	
	public static void loadSettings(String filename) {

		Properties properties = new Properties();
		try {
			File f = new File(filename);
			if (!f.exists()) {
				throw new Exception("Couldn't find properies file to get MW Live host address and port from: " + filename);
			}
			properties.load(new FileInputStream(f));

			liveHost = properties.getProperty("address").trim();
			livePort = Integer.parseInt(properties.getProperty("port"));

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void showClients() {
		for (int i = 0; i < clients.size(); i++) {
			System.err.print("\nClient #" + i + " :: " + clients.get(i) + " :: " + clients.get(i).getUserName());
		}
	}

	public void setChatRights(String uName, int rightID) {
		for (int i = 0; i < clients.size(); i++) {
			if (clients.get(i).getUserName().equals(uName)) {
				clients.get(i).setRights(rightID);
			}
		}
	}

	public boolean isBanned(String ip) {
		boolean blub = false;
		return blub;
	}

	public boolean existChatUser(String uName) {
		return existChatUser(uName, false);
	}

	public boolean existChatUser(String uName, boolean login) {
		int i = 0;
		int j = 0;
		int nums = (login ? 1 : 0);
		boolean exist = false;

		while (!exist && i < clients.size()) {
			if (clients.get(i).getUserName().equals(uName)) {
				j++;
			}
			if (j > nums) {
				exist = true;
			}
			i++;
		}

		return exist;
	}

	public String getUserIP(String uName) {
		int i = 0;
		String ip = "";
		boolean exist = false;

		while (!exist && i < clients.size()) {
			clients.get(i).getUserName().equals(uName);
			if (clients.get(i).getUserName().equals(uName)) {
				exist = true;
				ip = clients.get(i).getIP();
			}
			i++;
		}
		return (ip.equals("") ? "FAIL" : ip);
	}

	public int getChatUserRight(String uName) {
		int i = 0;
		int right = 0;
		boolean exist = false;

		while (!exist && i < clients.size()) {
			if (clients.get(i).getUserName().equals(uName)) {
				right = clients.get(i).getRights();
				exist = true;
			}
			i++;
		}

		return right;
	}

	private String createColor() {
		String col = "";
		for (int i = 0; i < 6; i++) {
			int ran = (int) Math.round(Math.random() * ((int) (i / 2) == (i / 2) ? 12 : 15));
			switch (ran) {
			case 10:
				col += "A";
				break;
			case 11:
				col += "B";
				break;
			case 12:
				col += "C";
				break;
			case 13:
				col += "D";
				break;
			case 14:
				col += "E";
				break;
			case 15:
				col += "F";
				break;
			default:
				col += Integer.toString(ran);
				break;
			}
		}
		return col;
	}
	
	public Set<Integer> getPorts() {
		return ports;
	}

	public void setPorts(Set<Integer> ports) {
		this.ports = ports;
	}

	public HashMap<String, GameInfo> getGames() {
		return games;
	}

	public void setGames(HashMap<String, GameInfo> games) {
		this.games = games;
	}

	public String getTopic() {
		return topic;
	}

	public void setTopic(String topic) {
		this.topic = topic;
	}

	private class ClientStatus {
		private String name = "";
		private int status = -1;
		private String statusMessage = "";
		private String color = "";
	}

	private class ChatThread extends Thread {

		private final static int TIMEOUT = 600000; // 10 Min 1000 * 60 * 10
		private Socket client;
		private String name;
		private int rights = 2;
		public BufferedReader in;
		public PrintWriter out;
		private String byeMessage = "";
		String clientAddr = "";
		private ClientStatus cs = new ClientStatus();

		final int RIGHT_ADMIN = 64;
		final int RIGHT_OPP = 32;
		//@SuppressWarnings("unused")
		final int RIGHT_OP = 16;
		//@SuppressWarnings("unused")
		final int RIGHT_USER = 2;
		final int RIGHT_UNAUTH = 1;
		final int RIGHT_NONE = 0;
		
		public ChatThread(Socket client) {
			this.client = client;
		}

		public int getRights() {
			return rights;
		}

		public void setRights(int rightID) {
			rights = rightID;
		}

		public String getUserName() {
			return name;
		}

		public String getIP() {
			return clientAddr;
		}

		@Override
		public void run() {
			clientAddr = client.getInetAddress().getHostAddress();
			int clientPort = client.getPort();

			try {
				client.setSoTimeout(TIMEOUT);
				in = new BufferedReader(new InputStreamReader(client.getInputStream()));
				out = new PrintWriter(client.getOutputStream(), true);

				login();
				System.out.println("connected to " + name + " @ " + clientAddr + ":" + clientPort);
				String message;
				String[] inComing;// 0: room; 1: type; 2: message

				while ((inComing = in.readLine().split("~.~")) != null && !this.isInterrupted()) {
					message = inComing[2];
					if (inComing[1].equals("server") && message.charAt(0) == '/') {
						if (parseCommand(message.substring(1), inComing[0]) && cs.status == 2) {
							try {
								client.setSoTimeout(TIMEOUT);
							} catch (SocketException se) {
							}
							// this.afk = false;
							cs.statusMessage = "";
							cs.status = 1;
						}
					} else if (inComing[1].equals("public") && rights > RIGHT_UNAUTH) {
						sendMessage(inComing[0] + "~.~" + inComing[1] + "~.~" + name + "~.~" + message);
						if (cs.status == 2) {
							try {
								client.setSoTimeout(TIMEOUT);
							} catch (SocketException se) {
							}
							// this.afk = false;
							cs.statusMessage = "";
							cs.status = 1;
						}
					}
				}

				in.close();
				out.close();
			} catch (IOException e) {
				System.err.println(e);
			} finally {
				logout();
				try {
					if (client != null)
						client.close();
				} catch (IOException e) {
				}
				System.out.println("Disconnected from " + clientAddr + ":" + clientPort + " : " + name);
			}
		}

		private boolean existUser(String uName) {
			boolean exist = false;
			return exist;
		}

		/*private int login(String userName, String password) {
			int result = 0;
			return result;
		}*/

		private void login() throws IOException {
			manager.add(out);
			name = in.readLine();

			if (name.length() >= 8 && name.substring(name.length() - 8, name.length()).equals("HTTP/1.1")) {
				http = true;
				out.println("<html>");
				out.println("\t<head>");
				out.println("\t\t<title>You need the MWLive client to join the Server!</title>");
				out.println("\t</head>");
				out.println("\t<body>");
				out.println("\t\tYou need the MWLive client to join this server!<br/>");
				out.println("\t</body>");
				out.println("</html>");
			} else {
				cs.name = name;
				cs.status = 1;
				cs.color = createColor();
				clientList.add(cs);
				if (existUser(name)) {
					out.println("none~.~server~.~none~.~!login auth &Please authentify!");
					rights = RIGHT_UNAUTH;
				} else if (!existChatUser(name, true) && !existUser(name)) {
					sendPlain("none~.~server~.~none~.~!joined " + cs.color + "&" + name);
				} else {
					out.println("none~.~server~.~none~.~!login fail true &Name already in use!");
					manager.remove(out);
				}
			}
		}

		private void logout() {
			if (http) {

			} else {
				clientList.remove(cs);

				if (!shutdown) {
					sendPlain("none~.~server~.~none~.~!left " + name + "&" + byeMessage);
					for (GameInfo game : games.values()) {
						if (game.creator.equals(name)) {
							games.remove(game.name);
						}
					}
				}
			}
			clients.remove(this);
			manager.remove(out);
			if (shutdown) {
				closeServer();
			}
		}

		private boolean setRights(String uname, String rightID) {
			boolean result = false;

			// if he is online, make it:
			setChatRights(uname, Integer.parseInt(rightID));

			return result;
		}

		public boolean parseCommand(String comm, String room) {
			boolean goOnline = false;
			String[] command = comm.split(" ");
			if (command[0].equals("shutdown") && rights >= RIGHT_ADMIN) {
				stopServer();
			} else if (command[0].equals("scl") && rights >= RIGHT_ADMIN) {
				showClients();
			} else if (command[0].equals("sr") && rights >= RIGHT_OPP) {
				if (command.length == 3) {
					if (setRights(command[1], command[2])) {
						out.println(room + "~.~server~.~none~.~!sr success " + command[1] + " " + command[2]);
					} else {
						out.println(room + "~.~server~.~none~.~!sr fail &Komisch O_o");
					}
				} else {
					out.println(room + "~.~server~.~none~.~!sr fail &Missing parameters!");
				}
			} else if (command[0].equals("me") && rights > RIGHT_UNAUTH) {
				sendPlain(room + "~.~server~.~none~.~!me " + name + (comm.length() > 3 ? "&" + comm.substring(3, comm.length()) : ""));
				goOnline = true;
			} else if (command[0].equals("afk") && rights > RIGHT_UNAUTH) {
				sendPlain(room + "~.~server~.~none~.~!afk " + name + "&" + (comm.length() > 4 ? comm.substring(4, comm.length()) : ""));
				try {
					client.setSoTimeout(0);// Timeout to 12h - 1000 * 60 * 60 *
											// 12
					cs.statusMessage = (comm.length() > 4 ? comm.substring(4, comm.length()) : "");
					cs.status = 2;
				} catch (SocketException se) {
				}
			} else if (command[0].equals("quit")) {
				out.println(room + "~.~server~.~none~.~!quit");
				if (command.length > 1) {
					byeMessage = comm.substring(5, comm.length());
				}
			} else if (command[0].equals("topic")) {
				if (command.length > 1) {
					topic = comm.substring(6, comm.length());
					sendPlain(room + "~.~server~.~none~.~!topic " + topic);
				}
			} else if (command[0].equals("userlist") && rights > RIGHT_NONE) {
				String tmp = "";
				if (command.length >= 2 && command[1].equals("plain")) {
					tmp = "userlist ";
					for (int i = 0; i < clientList.size(); i++) {
						ClientStatus cs = (ClientStatus) clientList.get(i);
						tmp += cs.name + "&" + cs.color + "&" + cs.status + "&" + cs.statusMessage;
						if (i < clientList.size() - 1) {
							tmp += "~";
						}
					}
				} else {
					tmp = "online user:<br/>";
					for (int i = 0; i < clientList.size(); i++) {
						ClientStatus cs = (ClientStatus) clientList.get(i);// cs
						tmp += cs.name;
						switch (cs.status) {
						case 2:
							tmp += " &lt;afk&gt; " + cs.statusMessage;
						}
						tmp += " <span style=\"color:#" + cs.color + ";\">" + cs.color + "</span>";
						if (i < clientList.size() - 1) {
							tmp += "<br/>";
						}
					}
				}
				out.println(room + "~.~server~.~none~.~!" + tmp);
			} else if (command[0].equals("help")) {
				out
						.println(room
								+ "~.~server~.~none~.~!help <ol><li>/me <i>msg</i> - Say what you are doing right now!</li><li>/afk <i>msg</i> - Lets you go idle with leaving a message</i></li><li>/quit <i>msg</i> - Lets you leave the server with leaving a message </li><li>/register <i>username password</i> - Let's you register an account on the current joined server.</li><li>/login <i>username password</i> - Let's you login with your registered account</li><li>/userlist - Let's you know which users are online.</li></ol>");//
			} else if (command[0].equals("create")) {
				if (command.length == 2) {
					boolean exist = false;
					for (GameInfo game: games.values()) {
						if (game.creator.equals(name)) {
							exist = true;
						}
					}
					if (!exist) {
						int port = generatePort(room);
						//int cajoPort = generatePort(room);
						int cajoPort = 1198;
						if (port == -1 || cajoPort == -1) {
							out.println(room + "~.~server~.~none~.~!Server is busy...");
							return goOnline;
						}
						MWServerStarter starter = new MWServerStarter(getHost(), port, cajoPort);
						Thread server = new Thread(starter);
						server.start();
						games.put(command[1], new GameInfo(server, command[1], port, cajoPort, new Date(), name));
						out.println(room + "~.~server~.~none~.~!Game has been created, display name: \"" + command[1] + "\". (Don't close MagicWars).");	
						sendPlain("none~.~server~.~none~.~!run port="+port+"&cajo="+cajoPort+"&create&host="+getHost()+"&nickname="+name);
					} else {
						out.println(room + "~.~server~.~none~.~!One user can create only one game. Use /remove &lt;display_name&gt; if you want to remove it for some reason.");
					}
				} else {
					out.println(room + "~.~server~.~none~.~!Missing parameters: /create &lt;display_name&gt;");
				}	
			} else if (command[0].equals("join")) {
				if (command.length == 2) {
					GameInfo info = games.get(command[1]);
					if (info != null) {
						out.println(room + "~.~server~.~none~.~!Game has been found, connecting...");	
						sendPlain("none~.~server~.~none~.~!run port="+info.socketPort+"&cajo="+info.cajoPort+"&join&host="+getHost()+"&nickname="+name);
					} else {
						out.println(room + "~.~server~.~none~.~!Couldn't find such game, name: " + command[1] + ". Try /games to get list of games available at the moment.");
					}
				} else {
					out.println(room + "~.~server~.~none~.~!Missing parameters: /join &lt;name&gt;");
				}	
			} else if (command[0].equals("games")) {

				String tmp = "<b>Games available to join:</b><br/>";
				if (games.size() == 0) {
					tmp += "<i>no game found.</i><br/>Create your own game (/create &lt;display_name&gt;) and wait for other players to join.";
				} else {
					tmp += "<ol>";
					for (GameInfo info : games.values()) {
						SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
						String date = sdf.format(info.created);
						tmp += "<li>name: <i>" + info.name + "</i>, created: <i>" + date + " [GMT+3]</i> by <i>"+info.creator+"</i></li>";
					}
					tmp += "</ol>";
				}
				out.println(room + "~.~server~.~none~.~!" + tmp);
			} else if (command[0].equals("games_raw")) {

				String tmp = "";
				if (games.size() == 0) {
					tmp = "empty";
				} else {
					for (GameInfo info : games.values()) {
						SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
						String date = sdf.format(info.created);
						if (!tmp.isEmpty()) tmp += "|3485|";
						info.clean();
						tmp += "name:" + info.name + ",created:" + date + " [GMT+3],creator:"+info.creator;
					}
				}
				out.println(room + "~.~server~.~none~.~!games " + tmp);
			} else {
				out.println(room + "~.~server~.~none~.~!command '" + comm + "' not found!");
			}
			return goOnline;
		}
		
		private String getHost() {
			return liveHost;
		}

		private int generatePort(String room) {
			int port = 10000 + (int)(9999*Math.random());
			int maxtry = 1000;
			while (ports.contains(port) && maxtry > 0) {
				port = 10000 + (int)(9999*Math.random());
				maxtry--;
			}
			if (maxtry == 0) {
				out.println(room + "~.~server~.~none~.~!Server is busy...");
				return -1;
			}
			
			return port;
		}
		
		private void sendMessage(String message) {
			synchronized (manager) {
				for (PrintWriter out : manager) {
					out.println(message);
				}
			}
		}

		private void sendPlain(String message) {
			synchronized (manager) {
				for (PrintWriter out : manager) {
					out.println(message);
				}
			}
		}
	}
	
	private class GameInfo {
		@SuppressWarnings("unused")
		private Thread server;
		private String name;
		private int socketPort;
		private int cajoPort;
		private Date created;
		private String creator;
		
		public GameInfo(Thread server, String name, int socket, int cajo, Date created, String creator) {
			this.server = server;
			this.name = name;
			this.socketPort = socket;
			this.cajoPort = cajo;
			this.created = created;
			this.creator = creator;
		}
		
		public void clean() {
			this.name = this.name.trim().replace(",", "");
			this.creator = this.creator.trim().replace(",", "");
		}
	}

	@Override
	public void run() {
		this.startServer();
	}

	public static void main(String[] args) {
		new LiveServer(livePort).startServer();
	}
}